
#include "lcd_touch_drv.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

/************************************************************************/
/************************************************************************/
/************************************************************************/
static drv_log_t _custom_log;
void lcd_touch_drv_log_cb(drv_log_t print_cb)
{
    _custom_log = print_cb;
}
void custom_log(const char* dsc, uint32_t value)
{
	if(_custom_log == NULL) return;
	_custom_log(dsc, value);
}

/************************************************************************/
static spi_device_handle_t dspi;
static spi_device_handle_t tspi;
static volatile bool spi_color_sent;
static volatile bool spi_trans_in_progress;
static void IRAM_ATTR spi_ready (spi_transaction_t *trans)
{
  spi_trans_in_progress = false;
  lv_disp_t * disp = _lv_refr_get_disp_refreshing();
  if (spi_color_sent) lv_disp_flush_ready(&disp->driver);
}

/************************************************************************/
void drv_spi_init(void)
{
  //---------------------------------------------------------------
  spi_bus_config_t buscfg = {
    .miso_io_num = DRV_SPI_MISO,
    .mosi_io_num = DRV_SPI_MOSI,
    .sclk_io_num = DRV_SPI_CLK,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = DISP_BUF_SIZE * 2,
  };
  spi_bus_initialize(VSPI_HOST, &buscfg, 2);
  custom_log("drv_spi_init：", 0);

  //---------------------------------------------------------------
  spi_device_interface_config_t ddevcfg = {
    .clock_speed_hz = 40 * 1000 * 1000, //Clock out at 40 MHz
    .mode = 0,                    //SPI mode 0
    .spics_io_num = DISP_SPI_CS,  //CS pin
    .queue_size = 1,
    .pre_cb = NULL,
    .post_cb = spi_ready,
    .flags = SPI_DEVICE_HALFDUPLEX,
  };
  spi_bus_add_device(VSPI_HOST, &ddevcfg, &dspi);
  custom_log("drv_spi_init：", 1);

  //---------------------------------------------------------------
  spi_device_interface_config_t tdevcfg = {
    .clock_speed_hz = 10 * 1000 * 1000, //Clock out at 10 MHz
    .mode = 0,                        //SPI mode 0
    .spics_io_num = TP_SPI_CS,        //CS pin
    .queue_size = 1,
    .pre_cb = NULL,
    .post_cb = NULL,
    //.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY,
  };
  spi_bus_add_device(VSPI_HOST, &tdevcfg, &tspi);
  custom_log("drv_spi_init：", 2);
}

/************************************************************************/
void disp_spi_send_data(uint8_t * data, uint16_t length)
{
  if (length == 0) return;
  while (spi_trans_in_progress);
  spi_transaction_t t = {
    .length = length * 8,
    .tx_buffer = data
  };
  spi_trans_in_progress = true;
  spi_color_sent = false;
  spi_device_queue_trans(dspi, &t, portMAX_DELAY);
}

/************************************************************************/
void disp_spi_send_colors(uint8_t * data, uint16_t length)
{
  if (length == 0) return;  //no need to send anything
  while (spi_trans_in_progress);
  spi_transaction_t t = {
    .length = length * 8, // transaction length is in bits
    .tx_buffer = data
  };
  spi_trans_in_progress = true;
  spi_color_sent = true;
  spi_device_queue_trans(dspi, &t, portMAX_DELAY);
}

/************************************************************************/
uint8_t tp_spi_xchg(uint8_t data_send)
{
  uint8_t data_recv = 0;
  spi_transaction_t t = {
    .length = 8, // length is in bits
    .tx_buffer = &data_send,
    .rx_buffer = &data_recv
  };

  spi_transaction_t * rt;
  spi_device_queue_trans(tspi, &t, portMAX_DELAY);
  spi_device_get_trans_result(tspi, &rt, portMAX_DELAY);
  return data_recv;
}

/************************************************************************/
/************************************************************************/
/************************************************************************/
typedef struct {
  uint8_t cmd;
  uint8_t data[16];
  uint8_t databytes;
} lcd_init_cmd_t;

/************************************************************************/
static void ili9341_send_cmd(uint8_t cmd)
{
  gpio_set_level(ILI9341_DC, 0);   /*Command mode*/
  disp_spi_send_data(&cmd, 1);
}
static void ili9341_send_data(void * data, uint16_t length)
{
  gpio_set_level(ILI9341_DC, 1);   /*Data mode*/
  disp_spi_send_data(data, length);
}
static void ili9341_send_color(void * data, uint16_t length)
{
  gpio_set_level(ILI9341_DC, 1);   /*Data mode*/
  disp_spi_send_colors(data, length);
}
static void ili9341_set_orientation(uint8_t orientation)
{
  //"PORTRAIT", "PORTRAIT_INVERTED", "LANDSCAPE", "LANDSCAPE_INVERTED"
  //uint8_t data[] = {0x6C, 0xEC, 0xCC, 0x4C};
  uint8_t data[] = {0x48, 0x88, 0x28, 0xE8};
  ili9341_send_cmd(0x36);
  ili9341_send_data((void *) &data[orientation], 1);
}

/************************************************************************/
void ili9341_init(void)
{
  lcd_init_cmd_t ili_init_cmds[] = {
    {0xCF, {0x00, 0x83, 0X30}, 3},
    {0xED, {0x64, 0x03, 0X12, 0X81}, 4},
    {0xE8, {0x85, 0x01, 0x79}, 3},
    {0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5},
    {0xF7, {0x20}, 1},
    {0xEA, {0x00, 0x00}, 2},
    {0xC0, {0x26}, 1},          /*Power control*/
    {0xC1, {0x11}, 1},          /*Power control */
    {0xC5, {0x35, 0x3E}, 2},    /*VCOM control*/
    {0xC7, {0xBE}, 1},          /*VCOM control*/
    {0x36, {0x28}, 1},          /*Memory Access Control*/
    {0x3A, {0x55}, 1},			/*Pixel Format Set*/
    {0xB1, {0x00, 0x1B}, 2},
    {0xF2, {0x08}, 1},
    {0x26, {0x01}, 1},
    {0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15},
    {0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15},
    {0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
    {0x2B, {0x00, 0x00, 0x01, 0x3f}, 4},
    {0x2C, {0}, 0},
    {0xB7, {0x07}, 1},
    {0xB6, {0x0A, 0x82, 0x27, 0x00}, 4},
    {0x11, {0}, 0x80},
    {0x29, {0}, 0x80},
    {0, {0}, 0xff},
  };

  //Initialize non-SPI GPIOs
  gpio_pad_select_gpio(ILI9341_DC);
  gpio_set_direction(ILI9341_DC, GPIO_MODE_OUTPUT);
  gpio_pad_select_gpio(ILI9341_RST);
  gpio_set_direction(ILI9341_RST, GPIO_MODE_OUTPUT);

  //Reset the display
  gpio_set_level(ILI9341_RST, 0);
  vTaskDelay(100 / portTICK_RATE_MS);
  gpio_set_level(ILI9341_RST, 1);
  vTaskDelay(100 / portTICK_RATE_MS);

  //Send all the commands
  uint16_t cmd = 0;
  while (ili_init_cmds[cmd].databytes != 0xff) {
    ili9341_send_cmd(ili_init_cmds[cmd].cmd);
    ili9341_send_data(ili_init_cmds[cmd].data, ili_init_cmds[cmd].databytes & 0x1F);
    if (ili_init_cmds[cmd].databytes & 0x80) {
      vTaskDelay(100 / portTICK_RATE_MS);
    }
    cmd++;
  }

  ili9341_set_orientation(DISP_ORIENTATION);
  //ili9341_send_cmd(0x21);
  ili9341_send_cmd(0x20);
  custom_log("ili9341_init：", 0);
}

/************************************************************************/
void ili9341_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map)
{
  uint8_t data[4];
  /*Column addresses*/
  ili9341_send_cmd(0x2A);
  data[0] = (area->x1 >> 8) & 0xFF;
  data[1] = area->x1 & 0xFF;
  data[2] = (area->x2 >> 8) & 0xFF;
  data[3] = area->x2 & 0xFF;
  ili9341_send_data(data, 4);

  /*Page addresses*/
  ili9341_send_cmd(0x2B);
  data[0] = (area->y1 >> 8) & 0xFF;
  data[1] = area->y1 & 0xFF;
  data[2] = (area->y2 >> 8) & 0xFF;
  data[3] = area->y2 & 0xFF;
  ili9341_send_data(data, 4);

  /*Memory write*/
  ili9341_send_cmd(0x2C);
  uint32_t size = lv_area_get_width(area) * lv_area_get_height(area);
  ili9341_send_color((void*)color_map, size * 2);
}

/************************************************************************/
void ili9341_sleep_in()
{
  uint8_t data[] = {0x08};
  ili9341_send_cmd(0x10);
  ili9341_send_data(&data, 1);
}

/************************************************************************/
void ili9341_sleep_out()
{
  uint8_t data[] = {0x08};
  ili9341_send_cmd(0x11);
  ili9341_send_data(&data, 1);
}

/************************************************************************/
/************************************************************************/
/************************************************************************/
int16_t avg_buf_x[XPT2046_AVG];
int16_t avg_buf_y[XPT2046_AVG];
uint8_t avg_last;

/************************************************************************/
static void xpt2046_corr(int16_t * x, int16_t * y)
{
  int16_t swap_tmp = *x;
  *x = *y;  *y = swap_tmp;
  if ((*x) > XPT2046_X_MIN)(*x) -= XPT2046_X_MIN;
  else(*x) = 0;
  if ((*y) > XPT2046_Y_MIN)(*y) -= XPT2046_Y_MIN;
  else(*y) = 0;
  (*x) = (uint32_t)((uint32_t)(*x) * LV_HOR_RES) /
         (XPT2046_X_MAX - XPT2046_X_MIN);
  (*y) = (uint32_t)((uint32_t)(*y) * LV_VER_RES) /
         (XPT2046_Y_MAX - XPT2046_Y_MIN);
}

/************************************************************************/
static void xpt2046_avg(int16_t * x, int16_t * y)
{
  /*Shift out the oldest data*/
  uint8_t i;
  for (i = XPT2046_AVG - 1; i > 0 ; i--) {
    avg_buf_x[i] = avg_buf_x[i - 1];
    avg_buf_y[i] = avg_buf_y[i - 1];
  }
  /*Insert the new point*/
  avg_buf_x[0] = *x;
  avg_buf_y[0] = *y;
  if (avg_last < XPT2046_AVG) avg_last++;
  /*Sum the x and y coordinates*/
  int32_t x_sum = 0;
  int32_t y_sum = 0;
  for (i = 0; i < avg_last ; i++) {
    x_sum += avg_buf_x[i];
    y_sum += avg_buf_y[i];
  }
  /*Normalize the sums*/
  (*x) = (int32_t)x_sum / avg_last;
  (*y) = (int32_t)y_sum / avg_last;
}

/************************************************************************/
void xpt2046_init(void)
{
  gpio_set_direction(XPT2046_IRQ, GPIO_MODE_INPUT);
  gpio_set_direction(TP_SPI_CS, GPIO_MODE_OUTPUT);
  gpio_set_level(TP_SPI_CS, 1);
  custom_log("xpt2046_init：", 0);
}

/************************************************************************/
bool xpt2046_read(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
  static int16_t last_x = 0;
  static int16_t last_y = 0;
  bool valid = true;
  uint8_t buf;

  int16_t x = 0;
  int16_t y = 0;
  uint8_t irq = gpio_get_level(XPT2046_IRQ);
  if (irq == 0) {
    gpio_set_level(TP_SPI_CS, 0);
    tp_spi_xchg(0b10010000);        /*Start x read*/
    buf = tp_spi_xchg(0);           /*Read x MSB*/
    x = buf << 8;
    buf = tp_spi_xchg(0b11010000);
    x += buf;
    buf =  tp_spi_xchg(0);   /*Read y MSB*/
    y = buf << 8;
    buf =  tp_spi_xchg(0);   /*Read y LSB*/
    y += buf;
    gpio_set_level(TP_SPI_CS, 1);

    /*Normalize Data*/
    x = x >> 3;
    y = y >> 3;
    custom_log("xpt2046.x address: ", x);
    custom_log("xpt2046.y address: ", y);
    xpt2046_corr(&x, &y);
    custom_log("corr.x address: ", x);
    custom_log("corr.y address: ", y);
    xpt2046_avg(&x, &y);
    custom_log("drv.x address: ", x);
    custom_log("drv.y address: ", y);
    last_x = x;
    last_y = y;
  } else {
    x = last_x;
    y = last_y;
    avg_last = 0;
    valid = false;
  }
  data->point.x = x;
  data->point.y = y;
  data->state = valid == false ? LV_INDEV_STATE_REL : LV_INDEV_STATE_PR;
  return false;
}

/************************************************************************/
/************************************************************************/
/************************************************************************/
void lcd_touch_drv_init()
{
  lv_init();
  drv_spi_init();
  ili9341_init();
  xpt2046_init();

  static lv_disp_draw_buf_t draw_buf;
  static lv_color_t disp_buf[DISP_BUF_SIZE];
  lv_disp_draw_buf_init(&draw_buf, disp_buf, NULL, DISP_BUF_SIZE);
  custom_log("lcd_touch_drv_init：", 1);

  //-------------------------------------------------------
  lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = DISP_HOR_RES;
  disp_drv.ver_res = DISP_VER_RES;
  disp_drv.flush_cb = ili9341_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);
  custom_log("lcd_touch_drv_init：", 2);

  //-------------------------------------------------------
  lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);
  indev_drv.read_cb = xpt2046_read;
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  lv_indev_drv_register(&indev_drv);
  custom_log("lcd_touch_drv_init：", 3);
}
